﻿module FsGame
open Microsoft.Xna.Framework.Graphics
open Microsoft.Xna.Framework.Input
open Microsoft.Xna.Framework

/// Represents user input
type User = { Mouse: MouseState; Keyboard: KeyboardState; GamePad: GamePadState; Window: Rectangle }

type Animation2DState = {
        mutable Texture: Texture2D;
        mutable Position: Vector2;
        mutable Color: Color }
type Animation2D =
    { LoadContent: GraphicsDevice -> Animation2DState -> unit; Update: User -> Animation2DState -> unit; State: Animation2DState }
    member self.CollsionRect =  
        Rectangle(int self.State.Position.X, int self.State.Position.Y, self.State.Texture.Width, self.State.Texture.Height)

type Direction =
    |Left
    |Right
    |Up
    |Down
    |And of Direction * Direction
module Key =
    let toDirection key =
        match key with
        |Keys.Up -> Up
        |Keys.Left -> Left
        |Keys.Right -> Right
        |Keys.Down -> Down
        |_         -> failwithf "%O is not an arrow key" key
    /// Returns true if the given key is an arrow key
    let isAnArrowKey key =
        match key with
        |Keys.Up 
        |Keys.Left 
        |Keys.Right 
        |Keys.Down -> true
        |_         -> false
/// Returns in which directions the arrow keys are pressed
let arrowKeyDirection u =
    u.Keyboard.GetPressedKeys()
    |> Array.filter Key.isAnArrowKey
    |> Array.map Key.toDirection
    |> Array.fold(fun (state: Direction option) (direction: Direction) ->
        match state with
        |Some state -> Some(And(state, direction))
        |None   -> Some direction) None

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module Animation2D =
    let withColor color anim = {anim with State = {anim.State with Color = color}}
    let withPosition x y anim = {anim with State = {anim.State with Position = Vector2(x,y)}}
    /// move the given animation the given amount every frame
    let move x y anim = {anim with Update = (fun u state -> anim.Update u state; state.Position <- state.Position + Vector2(x, y))}
    /// Creates a do nothing animation that has the given texture
    let import path = { LoadContent = (fun dev state -> state.Texture <- Texture2D.FromFile(graphicsDevice = dev, filename = path)); Update = (fun _ _ -> ()); State = { Texture = null; Position = Vector2(); Color = Color.White }}
    let whenSome x f anim =
        {anim with Update = (fun u state ->
                match x u with
                |Some x -> (f x anim).Update u state
                |None   -> anim.Update u state)}
    /// Moves the given animation in the given direction with the given # of pixels every frame
    let rec moveToThe num direc anim =
        match direc with
        |Left  -> move -num 0.0f anim
        |Right -> move num 0.0f anim
        |Up    -> move 0.0f -num anim
        |Down  -> move 0.0f num anim
        |And(d, d2) -> anim |> moveToThe num d |> moveToThe num d2
    
    let whenTrue x f anim =
        let an2 = lazy f anim
        {anim with Update = (fun u state ->
                if x u then an2.Force().Update u state
                else anim.Update u state)}
    /// Runs the animation as a single animation game
    let run anim =
        let spriteBatch: SpriteBatch ref = ref null
        let graphics = ref null
        use game =
            {new Game( )with
                member self.LoadContent() =
                    anim.LoadContent self.GraphicsDevice anim.State
                    spriteBatch := new SpriteBatch(self.GraphicsDevice)
                member self.Update time =
                    anim.Update { Mouse = Mouse.GetState(); Keyboard = Keyboard.GetState(); GamePad = GamePad.GetState(PlayerIndex.One); Window = self.Window.ClientBounds } anim.State
                member self.Draw time =
                    self.GraphicsDevice.Clear(Color.White)
                    let spriteBatch = !spriteBatch
                    spriteBatch.Begin()
                    spriteBatch.Draw(anim.State.Texture, anim.State.Position, anim.State.Color)
                    spriteBatch.End()
                    }
        graphics := new GraphicsDeviceManager(game)
        game.Run()
    
    
    
    /// Keeps the given animation inside the given rectangle.
    let keepWithin (boundingRect: Rectangle) anim =
        {anim with Update = (fun u state ->
                anim.Update u state
                if state.Position.X < float32 boundingRect.X then
                    state.Position.X <- float32 boundingRect.X
                elif (state.Position.X + float32 state.Texture.Width) > float32(boundingRect.X + boundingRect.Width) then
                    state.Position.X <- float32(boundingRect.X + boundingRect.Width - state.Texture.Width)
                
                if state.Position.Y < float32 boundingRect.Y then
                    state.Position.Y <- float32 boundingRect.Y
                elif (state.Position.Y + float32 state.Texture.Height) > float32(boundingRect.Y + boundingRect.Height) then
                    state.Position.Y <- float32(boundingRect.Y + boundingRect.Height - state.Texture.Height))}
    let computeUsingUser x f anim =
        whenSome (x >> Some) f anim            
    /// Keeps the given animation in between the given xs
    let keepWithinX startX stopX anim = 
        {anim with Update = (fun u state -> 
                anim.Update u state;  
                if state.Position.X > stopX then 
                    state.Position.X <- stopX;
                elif state.Position.X < startX then 
                    state.Position.X <- startX)}
/// Returns a rectangle representing the window of the game
let window u =
    let mutable res = u.Window
    res.X <- 0; res.Y <- 0
    res
let Keyboard key u = u.Keyboard.IsKeyDown key
